//
// See the file COPYING for redistribution information.
//

#ifndef __NAIPAX_HTTP_HPP__
#define __NAIPAX_HTTP_HPP__

#include <string>
#include <stdint.h>
#include <time.h>
#include <map>

namespace Naipax {
    /// This class is used to represent HTTP request.
    class HttpRequest {
    public:
        /** Build HTTP request.
         *
         * Currently, it supports the GET method only and takes no
         * more arguments other than host name and path name.
         *
         * @param pHost Host name
         * @param pPath Path name
         * @exception Error::INVALID_ARGUMENT if pHost is NULL or pPath
         *            is NULL. _mHeaders is set to empty.
         * @exception Error::DATA_OUT_OF_RANGE if the length of pHost
         *            exceeds URL_HOST_SIZE - 1 or the length of
         *            pPath exceeds URL_PATH_SIZE - 1.
         *            _mHeaders is set to empty.
         */
        void Build(const char *pHost, const char *pPath);

        /** Return the serialized headers.
         *
         * @return Serialized headers
         */
        const std::string& Headers() const { return _mHeaders; }
        
        /** Accessor to user agent string
         *
         * If _mUserAgent is set to an empty string, then the default
         * user agent will be used.
         */
        std::string& UserAgent() { return _mUserAgent; }

        /// Accept gzip encoding
        bool& AcceptGzip() { return _mAcceptGzip; }

        /** Clean up member variables
         *
         * This method does not clear _mUserAgent.
         */
        void Reset();

    private:
        std::string _mHost;
        std::string _mPath;
        std::string _mHeaders;
        std::string _mUserAgent;
        bool _mAcceptGzip;      // Default is true.
    };
};

namespace Naipax {
    /// This class is used to represent HTTP response. 
    class HttpResponse {
    public:
        /// Constructor
        HttpResponse(const std::string &rRespStr){
            Reset();
            mRespStr = rRespStr;
        }

        HttpResponse() {
            Reset();
        }

        /** Parse the status line for the status code.
         *
         * @return The status code
         * @return 0 if any error occurs.
         */
        static uint32_t GetStatusCode(const char *pStatusLine,
                                      uint32_t Len);
        static uint32_t GetStatusCode(std::string &rStatusLine);
        
        /** Parse the status line for the status code.
         *
         * @exception Error::CANNOT_BUILD_DATA if the status code is not
         *            found or there is no content or any other error occurs 
         */
        void ParseStatusLine();
        
        /** Parse the headers.
         *
         * This parser does not support duplicate
         * header field names and does not support multi-line field
         * values.
         *
         * @exception Error::CANNOT_BUILD_DATA if the status code is not
         *            found or there is no content or any other error occurs 
         */
        void ParseHeaders();

        /** Restore chunked body or gzipped body.
         *
         * If response is neither chunked nor gzipped, then this
         * method does nothing.
         */
        void RestoreBody();
        
        /// Clean up member variables
        void Reset();

        // 
        // The following members are transparent. Use with caution.
        //

        /// The whole response content.
        /// User needs to append received octets to this member.
        /// Parse*() acts on this variable.
        std::string mRespStr;

        /// The version of http (e.g. "HTTP/1.0", "HTTP/1.1")
        std::string mHttpVersion;

        /// Response status code
        uint32_t mStatus;
        
        /// The start position of headers
        uint32_t mHeaderStartPos;

        /// Has message body?
        bool mHasBody;
        /// The start position of message body
        uint32_t mBodyStartPos;

        /// Is Content-Length specified in headers?
        bool mHasContentLength;
        /// The specified content length
        uint32_t mContentLength;

        /// The web server's name
        std::string mServer;

        // The following two members come from 'Content-Type'.
        /// Media type (e.g. text/html, text/xml)
        std::string mMediaType;
        /// The character set of content (e.g. iso-8859-1)
        std::string mCharset;

        /// Is chunked ransfer encoding?
        bool mIsChunkedTransfer;
        
        /// The encoding of response data (e.g. gzip)
        bool mIsGzipped;

        /// The date string (e.g. "Tue, 15 Nov 1994 08:12:31 GMT")
        std::string mDateStr;
        /// The parsed timestamp of mDateStr
        time_t mDate;

        /// Location field
        std::string mLocation;

        /** Store other header fields? If this member is set to false,
         * then unrecognized headers will not be stored in
         * mOtherHeaders.
         */
        bool mStoreOtherHeaders;
        /// Other header fields
        std::map<std::string, std::string> mOtherHeaders;

    private:
        /** Parse a date/time string. Its recognized formats include:
         * RFC 822, RFC 850, and asctime()'s format.
         *
         * @param rDateStr
         * @exception Error::CANNOT_BUILD_DATA if rDateStr is not recognized.
         */
        void _ParseDate(std::string &rDateStr);

        /** Get chunk size from a line. This method converts
         * a hexadecimal into a decimal.
         *
         * @return Chunk size in decimal number
         */
        uint32_t _GetChunkSize(const char *pLine, uint32_t Len);
        
        /** Join data chunks. When chunks are successfully joined,
         * mIsChunkedTransfer is set to false.
         *
         * This method does nothing if mIsChunkedTransfer is false.
         *
         * @exception Error::CANNOT_BUILD_DATA
         */
        void _JoinChunks(std::string &rData);

        /** Gunzip data. When data is successfully uncompressed,
         * mIsGzipped is set to false.
         *
         * If mIsGzipped is true, this method does nothing.
         *
         * @param rData
         * @exception Error::CANNOT_UNCOMPRESS_DATA
         */
        void _GunzipData(std::string &rData);
    };
};

#endif // __NAIPAX_HTTP_HPP__
